#ifndef __POLYTASK_H__
#define __POLYTASK_H__

#include "tbb/combinable.h"
#include "cnc/cnc.h"
#include "cnc/default_tuner.h"

using namespace std;

#include "cnc/debug.h"

// forward declaration
class polyTask_context;

#include "TaskTag.hpp"
#include "Threading.hpp"
#include "Timing.hpp"

// PolyTask core class
template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM> class polyTask_step {
public:
  mutable tbb::combinable<bool> initialized; // make private someday
  polyTask_step() : initialized(false) {}

  int64_t execute
  (const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &, polyTask_context &) const;
};

// PolyTask tuner class
template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM> class polyTask_tuner : 
  public CnC::step_tuner<>, public CnC::tag_tuner<>, public CnC::hashmap_tuner {
public:
  mutable tbb::combinable<bool> initialized; // make private someday

  polyTask_tuner() : initialized(false) {}
  template<class DC>
  void depends(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &, 
               polyTask_context &, DC &) const;
};

//template<>
template< int TASK_ID, int TASKTAGDIM, int INTERTASKDIM >
struct cnc_tag_hash_compare< TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> >
{
  size_t hash(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& tt) const
  {
    return  tt[0] * 1129 +  tt[1] * 491  + tt[2] * 197 + tt[3] * 19 + tt[4];
  }

  bool equal(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& a, const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& b) const
  {
    return a == b;
  }
};

// Context for a single polyTask 
class polyTask_context :
public CnC::context< polyTask_context> {
public:
  void *wA;
  double overheadGlobal; // make private someday
  MasterTimer masterTimerGlobal; // make private someday
  double overheadCNCGetPut; // make private someday
  MasterTimer masterTimerCNCGetPut; // make private someday
  double overheadCNCDep; // make private someday
  MasterTimer masterTimerCNCDep; // make private someday

  polyTask_tuner <0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> m_tuner;
  CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        void *, polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> > items;
  CnC::step_collection< polyTask_step<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                        > m_steps;
  CnC::tag_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                       polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                       > m_tags;

  polyTask_tuner <1, TASKTAGDIMMACRO, INTERTASKDIMMACRO> m_tuner2;
  CnC::item_collection< TaskTag<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        void *, polyTask_tuner<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO> > items2;
  CnC::step_collection< polyTask_step<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        polyTask_tuner<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                        > m_steps2;
  CnC::tag_collection< TaskTag<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                       polyTask_tuner<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                       > m_tags2;

  CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        void *, polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> >&
  getItemCollection(const TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> &t) {
    return items;
  }

  CnC::item_collection< TaskTag<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        void *, polyTask_tuner<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO> >&
  getItemCollection(const TaskTag<1, TASKTAGDIMMACRO, INTERTASKDIMMACRO> &t) {
    return items2;
  }


  polyTask_context(void *ptr) :
    wA(ptr),
    overheadCNCDep(0.0), masterTimerCNCDep(),
    overheadGlobal(0.0), masterTimerGlobal(), 
    overheadCNCGetPut(0.0), masterTimerCNCGetPut(),
    m_tuner(), items(*this, "items", m_tuner), m_tags(*this), m_steps(*this),
    m_tuner2(), items2(*this, "items2", m_tuner2), m_tags2(*this), m_steps2(*this)
  {

    Threading::dumpThreadInfo(std::cerr, " polytask_init ");

    // prescription
    m_tags.prescribes( m_steps, *this );
    m_tags2.prescribes( m_steps2, *this );

    // producer/consumer
    m_steps.consumes( items );
    m_steps.produces( items );
    m_steps2.consumes( items2 );
    m_steps2.produces( items2 );
  }

// public:
//   polyTask_tuner <0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> m_tuner;
//   CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>, 
//                         void *, polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> > M;
//   CnC::step_collection< polyTask_step<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>, 
//                         polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> 
//                         > m_steps;
//   CnC::tag_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> , 
//                        polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> 
//                        > m_tags;

//   CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>, 
//                         void *, polyTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> >&
//   getItemCollection(const TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> &t) {
//     return M;
//   }  

//   polyTask_context() : 
//     m_tuner(), 
//     M(*this, "M", m_tuner),  
//     m_tags(*this), 
//     m_steps(*this)
//   {

//     Threading::dumpThreadInfo(std::cerr, " polytask_init ");

//     // prescription
//     m_tags.prescribes( m_steps, *this );

//     // producer/consumer
//     m_steps.consumes( M );
//     m_steps.produces( M );
//   }

};


#endif
